﻿using System;
using System.Security.Cryptography;

namespace Shared
{
    public class CRC32 : HashAlgorithm
    {
        public const UInt32 DefaultPolynomial = 0xedb88320;
        public const UInt32 DefaultSeed = 0xffffffff;

        private UInt32 hash;
        private UInt32 Seed;
        private UInt32[] Table;
        private static UInt32[] DefaultTable;

        public CRC32()
        {
            Table = InitializeTable(DefaultPolynomial);
            Seed = DefaultSeed;

            Initialize();
        }

        public CRC32(UInt32 polynomial, UInt32 seed)
        {
            Table = InitializeTable(polynomial);
            this.Seed = seed;

            Initialize();
        }

        public override void Initialize()
        {
            hash = Seed;
        }

        protected override void HashCore(byte[] buffer, int start, int length)
        {
            hash = CalculateHash(Table, hash, buffer, start, length);
        }

        protected override byte[] HashFinal()
        {
            byte[] hashBuffer = BitConverter.GetBytes(~hash);
            this.HashValue = hashBuffer;

            return hashBuffer;
        }

        public override int HashSize
        {
            get { return 32; }
        }

        public static UInt32 Compute(byte[] buffer)
        {
            return ~CalculateHash(InitializeTable(DefaultPolynomial), DefaultSeed, buffer, 0, buffer.Length);
        }

        public static UInt32 Compute(UInt32 seed, byte[] buffer)
        {
            return ~CalculateHash(InitializeTable(DefaultPolynomial), seed, buffer, 0, buffer.Length);
        }

        public static UInt32 Compute(UInt32 polynomial, UInt32 seed, byte[] buffer)
        {
            return ~CalculateHash(InitializeTable(polynomial), seed, buffer, 0, buffer.Length);
        }

        private static UInt32[] InitializeTable(UInt32 polynomial)
        {
            if (polynomial == DefaultPolynomial && DefaultTable != null)
                return DefaultTable;

            UInt32[] createTable = new UInt32[256];

            for (int i = 0; i < 256; i++)
            {
                UInt32 entry = (UInt32)i;

                for (int j = 0; j < 8; j++)
                    if ((entry & 1) == 1)
                        entry = (entry >> 1) ^ polynomial;

                    else
                        entry = entry >> 1;

                createTable[i] = entry;
            }

            if (polynomial == DefaultPolynomial)
                DefaultTable = createTable;

            return createTable;
        }

        private static UInt32 CalculateHash(UInt32[] table, UInt32 seed, byte[] buffer, int start, int size)
        {
            UInt32 crc = seed;

            for (int i = start; i < size; i++)
                unchecked
                {
                    crc = (crc >> 8) ^ table[buffer[i] ^ crc & 0xff];
                }

            return crc;
        }
    }
}
